Skip to content

Conversation

BillTompkins
Copy link
Contributor

Hi! I've made a collection of a few utility argument types that I've found useful over the years. I think it would be useful to have them in cmd2 itself so users can find them easily and not re-invent this wheel.

  • integer: like 'int', but accepts arbitrary bases (i.e. 0x10), and optional suffix like "10K" or "64Mi"
  • hexadecimal: base 16 integer, with nicer error text than using a lambda
  • Range: integer from specified range, when that may be too large for 'choices'
  • IntSet: set of integers from specified range

integer

Usage

parser.add_argument('value', type=integer)
testapp> testinteger 5
Value 5
testapp> testinteger 0x10
Value 16
testapp> testinteger 0b1000_0000
Value 128
testapp> testinteger 10G
Value 10000000000
testapp> testinteger 10Mi
Value 10485760

Failures

testapp> testinteger fooey
Usage: testinteger [-h] value
Error: argument value: invalid integer value: 'fooey'

Limitations

Because the base can be specified, Python does not allow a leading zero. This avoids confusion where a user might expect the "C" behavior of that specifying an octal value, or expect the "normal" behavior of ignoring the leading zero.
The default int constructor only uses decimal, so allows the leading zero.

testapp> testinteger 060
Usage: testinteger [-h] value
Error: argument value: invalid integer value: '060'

Notes

It is tempting to allow a "B" after the suffixes, since my typical use case has been specifying sizes in bytes, but I suspect there would be other similar special cases.

hexadecimal

Usage

parser.add_argument('value', type=hexadecimal)
testapp> testhexadecimal 10
Value 16
testapp> testhexadecimal 0x10
Value 16

Failures

testapp> testhexadecimal fooey
Usage: testhexadecimal [-h] value
Error: argument value: invalid hexadecimal value: 'fooey'

Note

We could avoid having a standalone function for this, and instead the user could use:

parser.add_argument('value', type=lambda x: int(x, 16))

Unfortunately, the error text then becomes

Error: argument value: invalid <lambda> value: 'fooey'

Similarly for other one-liner strategies using partial evaluation.

Range

Usage

parser.add_argument('value', type=Range(10000)
testapp> testrange 5
Value 5

Failures

testapp> testrange -1
Usage: testrange [-h] value
Error: argument value: invalid Range[0..9999] value: '-1'

Notes

  • For small ranges, type=int, choices=range(5) works well, but it becomes unwieldy for large ranges of valid values.
  • Making this a class allows the Error text to be customized based on the range.
  • I don't love the name, as the actual value of the argument is an integer from the range, not the range itself. That said, it makes the syntax of the constructor fairly obvious (same as the range built-in), and is compact.

IntSet

Allows the user to specify multiple values from within a range, in a flexible format.

Usage

parser.add_argument('value', type=IntSet(100)
testapp> testintset 5
Value [5]
testapp> testintset 1,3,5,10-15
Value [1, 3, 5, 10, 11, 12, 13, 14, 15]
testapp> testintset all
Value range(0, 100)

Errors

testapp> testintset 100
Usage: testintset [-h] value
Error: argument value: invalid IntSet[0..99] value: '100'

Notes

  • Could use yield / yield from to return an iterable without constructing a list. Unfortunately, then the value checks are only performed when the iteration is done by the user code, not when the argument is being parsed. So we would need another function/generator to do the yielding, returned by this function after the parsing. It's not that much extra code, but it seems like an unlikely corner case that the user would be specifying a range so large that this matters.
  • Again, I don't love the name. It is returning an iterable (typically a list, but sometimes a range), not a set, and there is no uniqueness check on the values. But I don't really like IntList any more than IntSet.
  • Since we have control of the error text with __repr__, we could aim for error text like Error: argument value: invalid set of values from [0..99] value:'100' but I was trying to stay a little closer to the spirit of __repr__

P.S.

Thanks for all the work on cmd2! I've used it several times over the years for internal tools, and users are always so impressed with the polish (esp. help and tab completion). I've glued in rich in the past and have been looking forward to 3.0, I think that'll be great.

@tleonhardt
Copy link
Member

tleonhardt commented Sep 18, 2025

@BillTompkins Hey Bill, its nice to make your acquaintance.

Most large cmd2 applications have some sort of similar utility collection of argument types and validators. While we have been tempted to add something like this to cmd2, we have resisted the temptation because fundamentally each project does it a little bit differently and there isn't one true holy way we can see to do it right which will be universally useful. Fundamentally this is a generically useful argparse extension that isn't specific to cmd2. I know of some projets that are doing something similar but where they are using pydantic validators.

That being said, I think this does provide an excellent example for how to do this sort of thing which would be of use to many cmd2 users.

What would you think about moving this to the examples/ section and outside of cmd2 proper? You could combine it into a single Python file was an example. And add a link to it from the Documentation with a little bit more info about it.

@BillTompkins
Copy link
Contributor Author

@BillTompkins Hey Bill, its nice to make your acquaintance.

Most large cmd2 applications have some sort of similar utility collection of argument types and validators. While we have been tempted to add something like this to cmd2, we have resisted the temptation because fundamentally each project does it a little bit differently and there isn't one true holy way we can see to do it right which will be universally useful. Fundamentally this is a generically useful argparse extension that isn't specific to cmd2. I know of some projets that are doing something similar but where they are using pydantic validators.

That being said, I think this does provide an excellent example for how to do this sort of thing which would be of use to many cmd2 users.

What would you think about moving this to the examples/ section and outside of cmd2 proper? You could combine it into a single Python file was an example. And add a link to it from the Documentation with a little bit more info about it.

Sure, I think that makes sense, I'll put that together.

- integer: like 'int', but accepts arbitrary bases (i.e. 0x10), and optional
  suffix like "10K" or "64Mi"
- hexadecimal: base 16 integer, with nicer error text than using a lambda
- Range: integer from specified range, when that may be too large for 'choices'
- IntSet: set of integers from specified range
@tleonhardt tleonhardt merged commit 8a5d1bc into python-cmd2:main Sep 19, 2025
36 checks passed
@tleonhardt
Copy link
Member

@BillTompkins Thanks for the PR!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants